// FB Alpha - Emulator for MC68000/Z80 based arcade games
//            Refer to the "license.txt" file for more info

// Main module

//#define APP_DEBUG_LOG			// log debug messages to zzBurnDebug.html

#include "burner.h"
#include "cmd_line.h"
#include "imageload+.h"
#include "imageresize.h"
#include "maphkeys.h"
#include "config.h"

#ifdef _MSC_VER
 #ifdef _DEBUG
  #define _CRTDBG_MAP_ALLOC
  #include <crtdbg.h>
 #endif

 #pragma comment(lib, "comctl32")
 #pragma comment(lib, "winmm")
#endif

#include "version.h"

HINSTANCE hAppInst = NULL;		// Application Instance
HANDLE hMainThread;
int appThreadPriority = THREAD_PRIORITY_NORMAL;
int appShowCmd;

wchar_t appBurnVer[16];
wchar_t appExeName[EXE_NAME_SIZE + 1];

bool alwaysProcessKey = false;
static HMODULE hModRich20A = NULL;

// Used for the load/save dialog in commdlg.h (savestates, input replay, wave logging)
wchar_t commonChoice[MAX_PATH];
OPENFILENAME ofn;

#if defined (FBA_DEBUG)
 static char consoleBuffer[1024];
 static int prevConsoleStatus = -1;

 static HANDLE DebugBuffer;
 static FILE* DebugLog = NULL;
 static bool echoLog = true;
#endif

// Debug printf to a file
static int __cdecl AppDebugPrintf(int status, const char* format, ...)
{
#if defined (FBA_DEBUG)
	va_list vaFormat;

	va_start(vaFormat, format);

	if (DebugLog) {
		if (status != prevConsoleStatus) {
			switch (status) {
				case PRINT_ERROR:
					fprintf(DebugLog, "</font><font color=#FF3F3F>");
					break;
				case PRINT_IMPORTANT:
					fprintf(DebugLog, "</font><font color=#000000>");
					break;
				default:
					fprintf(DebugLog, "</font><font color=#009F00>");
			}
		}
		vfprintf(DebugLog, format, vaFormat);
		fflush(DebugLog);
	}

	if (!DebugLog || echoLog) {
		vsnprintf(consoleBuffer, 1024, format, vaFormat);

		if (status != prevConsoleStatus) {
			switch (status) {
				case PRINT_UI:
					SetConsoleTextAttribute(DebugBuffer,                                                       FOREGROUND_INTENSITY);
					break;
				case PRINT_IMPORTANT:
					SetConsoleTextAttribute(DebugBuffer, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
					break;
				case PRINT_ERROR:
					SetConsoleTextAttribute(DebugBuffer, FOREGROUND_RED | FOREGROUND_GREEN |                   FOREGROUND_INTENSITY);
					break;
				default:
					SetConsoleTextAttribute(DebugBuffer,                  FOREGROUND_GREEN |                   FOREGROUND_INTENSITY);
			}
		}

		WriteConsoleA(DebugBuffer, consoleBuffer, strlen(consoleBuffer), NULL, NULL);
	}

	prevConsoleStatus = status;

	va_end(vaFormat);
#endif

	return 0;
}

int dprintf(char* format, ...)
{
#if defined (FBA_DEBUG)
	va_list vaFormat;
	va_start(vaFormat, format);

	vsnprintf(consoleBuffer, 1024, format, vaFormat);

	if (prevConsoleStatus != PRINT_UI) {
		if (DebugLog) {
			fprintf(DebugLog, "</font><font color=#9F9F9F>");
		}
		SetConsoleTextAttribute(DebugBuffer, FOREGROUND_INTENSITY);
		prevConsoleStatus = PRINT_UI;
	}

	if (DebugLog) {
		fprintf(DebugLog, consoleBuffer);
		fflush(DebugLog);
	}
	WriteConsoleA(DebugBuffer, consoleBuffer, strlen(consoleBuffer), NULL, NULL);
	va_end(vaFormat);
#else
	(void)format;
#endif

	return 0;
}

void closeDebugLog()
{
#if defined (FBA_DEBUG)
	if (DebugLog) {
		fprintf(DebugLog, "</pre></body></html>");

		fclose(DebugLog);
		DebugLog = NULL;
	}

	if (DebugBuffer) {
		CloseHandle(DebugBuffer);
		DebugBuffer = NULL;
	}

	FreeConsole();
#endif
}

int openDebugLog()
{
#if defined (FBA_DEBUG)
 #if defined (APP_DEBUG_LOG)

    time_t nTime;
	time(&nTime);
	tm* tmTime = localtime(&nTime);

	{
		// Initialise the debug log file

		DebugLog = fopen("zzBurnDebug.html", "wb");

		if (ftell(DebugLog) == 0) {
			fprintf(DebugLog, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">");
			fprintf(DebugLog, "<html><head><meta http-equiv=Content-Type content=\"text/html\"></head><body><pre>");
		}

		fprintf(DebugLog, "</font><font size=larger color=#000000>");
		fprintf(DebugLog, ("Debug log created by " APP_TITLE " v%.20s on %s\n<br>"), appBurnVer, asctime(tmTime));
	}
 #endif

	{
		// Initialise the debug console

		COORD DebugBufferSize = { 80, 1000 };

		{
			// Since AttachConsole is only present in Windows XP, import it manually

#if defined (_MSC_VER)
// #error Manually importing AttachConsole() function, but compiling with _WIN32_WINNT >= 0x0500
			if (!AttachConsole(ATTACH_PARENT_PROCESS)) {
				AllocConsole();
			}
#else
 #define ATTACH_PARENT_PROCESS ((DWORD)-1)

			BOOL (WINAPI* pAttachConsole)(DWORD dwProcessId) = NULL;
			HMODULE hKernel32DLL = LoadLibrary(L"kernel32.dll"));

			if (hKernel32DLL) {
				pAttachConsole = (BOOL (WINAPI*)(DWORD))GetProcAddress(hKernel32DLL, "AttachConsole");
			}
			if (pAttachConsole) {
				if (!pAttachConsole(ATTACH_PARENT_PROCESS)) {
					AllocConsole();
				}
			} else {
				AllocConsole();
			}
			if (hKernel32DLL) {
				FreeLibrary(hKernel32DLL);
			}

 #undef ATTACH_PARENT_PROCESS
#endif
		}

		DebugBuffer = CreateConsoleScreenBuffer(GENERIC_WRITE, FILE_SHARE_READ, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
		SetConsoleScreenBufferSize(DebugBuffer, DebugBufferSize);
		SetConsoleActiveScreenBuffer(DebugBuffer);
		SetConsoleTitle(APP_TITLE L" Debug console");

		SetConsoleTextAttribute(DebugBuffer, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
		_snprintf(consoleBuffer, sizearray(consoleBuffer), "Welcome to the %ls debug console.\n", APP_TITLE);
		WriteConsoleA(DebugBuffer, consoleBuffer, strlen(consoleBuffer), NULL, NULL);

		SetConsoleTextAttribute(DebugBuffer, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
		if (DebugLog) {
			_snprintf(consoleBuffer, sizearray(consoleBuffer), "Debug messages are logged in zzBurnDebug.html");
			if (!DebugLog || echoLog) {
				_snprintf(consoleBuffer + strlen(consoleBuffer), 1024 - strlen(consoleBuffer), ", and echod to this console");
			}
			_snprintf(consoleBuffer + strlen(consoleBuffer), 1024 - strlen(consoleBuffer), ".\n\n");
		} else {
			_snprintf(consoleBuffer, sizearray(consoleBuffer), "Debug messages are echod to this console.\n\n");
		}
		WriteConsoleA(DebugBuffer, consoleBuffer, strlen(consoleBuffer), NULL, NULL);
	}

	prevConsoleStatus = -1;

	bprintf = AppDebugPrintf;						// Redirect Burn library debug to our function
#endif

	return 0;
}

static int appInit()
{
#if defined (_MSC_VER) && defined (_DEBUG)
	// Check for memory corruption
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
	_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG);
#endif

#ifndef _MSC_VER
	openDebugLog();
#endif

	// Create a handle to the main thread of execution
	DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &hMainThread, 0, false, DUPLICATE_SAME_ACCESS);

	// Load config for the application
	Config::load();
	Config::setSaveEnable(true);

	setWindowAspect();

	GDIPInit();

	// Set random skin
	randomSelectSkin();

	splashCreate();

	localiseInit(szLanguage);

	hModRich20A = LoadLibrary(L"riched20.dll");

	// Set the thread priority for the main thread
	SetThreadPriority(GetCurrentThread(), appThreadPriority);

	// Init the Burn library
	BurnLibInit();

	if (videoInterface.select(vidSelect)) {
		videoInterface.select(L"DirectDraw");
	}
	if (audioInterface.select(audSelect)) {
		audioInterface.select(L"DirectSound");
	}
	if (inputInterface.select(inputSelect)) {
		inputInterface.select(L"DirectInput");
	}

	// Build the ROM information
	createROMInfo();

	// set interface
	burnInterface.setIAutofire(&autofire);
	burnInterface.setIPatch(&patcher);
	burnInterface.setIRomLoader(&romLoader);
	BurnReinitScrn = scrnReinit;

	return 0;
}

static int appExit()
{
	BurnerDrvExit();				// Make sure any game driver is exitted
	mediaExit();					// Exit media
	scrnExit();						// Exit the screen window
	BurnLibExit();					// Exit the Burn library

	if (hModRich20A) {
		FreeLibrary(hModRich20A);
	}

	localiseExit();

	splashDestroy(1);

	GDIPShutdown();

	CloseHandle(hMainThread);

	closeDebugLog();

	return 0;
}

void appCleanup()
{
	stopReplay();
	waveLogStop();
	aviStop();

	appExit();
}

int appMessage(MSG* msg)
{
	// modeless dialog
	if (dialogManager.isDlgMessage(msg)) {
		return 0;
	}

	// modeless property sheet
	if (propertySheetMessage(msg)) {
		return 0;
	}

	return 1; // Didn't process this message
}

bool appProcessKeyboardInput()
{
	return true;
}

// Main program entry point
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR lpCmdLine, int nShowCmd)
{
	// Provide a custom exception handler
	SetUnhandledExceptionFilter(ExceptionFilter);

	hAppInst = hInstance;

	// Make version string
	if (burnVersion & 0xFF) {
		// private version (alpha)
		swprintf(appBurnVer, L"%x.%x.%x.%02x", burnVersion >> 20, (burnVersion >> 16) & 0x0F, (burnVersion >> 8) & 0xFF, burnVersion & 0xFF);
	} else {
		// public version
		swprintf(appBurnVer, L"%x.%x.%x", burnVersion >> 20, (burnVersion >> 16) & 0x0F, (burnVersion >> 8) & 0xFF);
	}

	appShowCmd = nShowCmd;

	appDirectory();				// Set current directory to be the applications directory

	createNeedDir();			// Make sure there are roms and cfg subdirectories

	{
		INITCOMMONCONTROLSEX initCC = {
			sizeof(INITCOMMONCONTROLSEX),
			ICC_BAR_CLASSES | ICC_COOL_CLASSES | ICC_LISTVIEW_CLASSES | ICC_PROGRESS_CLASS | ICC_TREEVIEW_CLASSES,
		};

		if (!InitCommonControlsEx(&initCC)) {
			InitCommonControls();
		}
	}

	if (!appInit()) {			// Init the application
		if (!processCmdLine(lpCmdLine)) {
			mediaInit();

			runMessageLoop();	// Run the application message loop
		}
	}

	appExit();					// Exit the application

	Config::save();				// Save config for the application

	return 0;
}
